﻿using System;
using System.Collections.Generic;
using System.Net;
using System.Net.Sockets;
using Tools.Debugging;
using System.Security.Authentication;
using repliback4.Net.Protocol.SASLMechanisms;
using repliback4.Misc.Queue;
using repliback4.Net.Protocol.Messages;
using repliback4.Net.Identification;
using System.Threading;
using repliback4.Net.Protocol.InfoQueryHandling;
using repliback4.Misc;
using repliback4.Net.Protocol.InfoQueryHandling.ServerSide;


namespace repliback4.Net.ServerSide {

    public class ServerSettings {
        ConfigFile cfgf;
        public ServerSettings ( ConfigFile cfgf ) {
            this.cfgf = cfgf;
        }

        #region Getters / Setters
        public int Port {
            get { return this.cfgf["net/port"].AsInt; }
            // set { this.localEndpoint.Port = value; }
        }
        public IPAddress Address {
            // set { this.localEndpoint.Address = value; }
            get {
                try {
                    return IPAddress.Parse( this.cfgf["net/address"] );
                } catch ( Exception ) { }
                return IPAddress.Any;
            }
        }
        public IPEndPoint LocalEndpoint {
            get { return new IPEndPoint( this.Address, this.Port ); }
        }

        public Host HostName {
            get { return this.cfgf["net/hostname"].ToString(); }
        }
        static public ServerSettings Default {
            get { return new ServerSettings( new ConfigFile( "cfg.xml", "server" ) ); }
        }
        #endregion

    }

    public class QueryFrom<T> {
        public InfoQuery Query;
        public T Sender;
    }

    public class QueryFromClient : QueryFrom<Client> {

    }


    public enum ServerQueueDirection { FromClient, ToClient }
    public class Server {


        protected int ClientCount = 0;
        protected ServerSettings settings;
        public ServerSettings Settings { get { return this.settings; } }
        protected TcpListener listener;

        public MechanismsRegistry authMechanism = MechanismsRegistry.Default;
        protected Net.Protocol.InfoQueryHandling.ServerSide.ServerInfoQueryHandlerManager iqhManager;
        //protected Queue<IRequest> requestQueue = new Queue<IRequest>( );

        protected QueueManager<QueryFromClient, ServerQueueDirection> queue;
        public QueueManager<QueryFromClient, ServerQueueDirection> Queue { get { return this.queue; } }

        protected List<Client> clientList = new List<Client>( );
        protected SessionManager sesMan = new SessionManager( );

        #region Constructors
        public Server ( ServerSettings settings ) {
            Logger.Created( this );
            this.settings = settings;
            this.Init( );
        }

        public Server ( ) {
            Logger.Created( this );
            this.settings = ServerSettings.Default;
            this.Init( );
        }
        #endregion

        protected void Init ( ) {
            this.InitQueue( );
            this.InitIQHandlers( );

        }

        protected void InitIQHandlers ( ) {
            iqhManager = new ServerInfoQueryHandlerManager( this );
            BindInfoQueryHandler.Register( iqhManager );
            SessionInfoQueryHandler.Register( iqhManager );
            //iqhManager.Add( typeof( Bind ).GetReplibackTagName( ), new BindInfoQueryHandler( iqhManager ) );
            //iqhManager.Add( typeof( Session ).GetReplibackTagName( ), new SessionInfoQueryHandler( iqhManager ) );


        }

        protected void InitQueue ( ) {
            this.queue = new QueueManager<QueryFromClient, ServerQueueDirection>( );
            this.queue.CreateQueue( ServerQueueDirection.FromClient );
            //this.queue.CreateQueue( ServerQueueDirection.FromClient );
            //Thread.Sleep( 1000 );
            this.queue.AddOnItemInQueueHandler( ServerQueueDirection.FromClient, new AutoQueue<QueryFromClient>.OnItemInQueueHandler( this.handleQueryFromClient ) );
        }

        private void handleQueryFromClient ( QueryFromClient item ) {
            Logger.Debug( this, "handling query {0} from {1}", item.Query.TagName, item.Sender.ClientInfo.Rid );
            this.iqhManager.Handle( item );
        }

        public void Start ( ) {
            try {
                Logger.Debug( this, "Server starting..." );
                this.listener = new TcpListener( this.settings.LocalEndpoint );
                this.listener.Start( );
                this.listenerStart( this.listener );
                Logger.Debug( this, "Server started." );
            } catch ( SocketException e ) {
                Logger.Debug( this, String.Format( "Server start failed. [{0}]", e.SocketErrorCode ) );
            }
        }

        protected void listenerStart ( TcpListener listener ) {
            Logger.Debug( this, "Setting connection callback..." );
            listener.BeginAcceptTcpClient( new AsyncCallback( this.acceptClient ), this.listener );
        }

        protected void acceptClient ( IAsyncResult result ) {
            Thread.CurrentThread.Name = "client" + ( this.ClientCount++ );
            try {
                Logger.Debug( this, "client accepted" );
                TcpListener listener = ( TcpListener ) result.AsyncState;
                TcpClient tcpClient = listener.EndAcceptTcpClient( result );
                Client client = new Client( this, tcpClient );
                this.clientList.Add( client );

                Logger.Debug( this, "clients connected: {0}", this.clientList.Count );
                client.Start( );
            } catch ( SocketException e ) {
                Logger.Exception( this, String.Format( "acceptClient SocketException: [{0}] :: [{1}]", e.SocketErrorCode, e.Message ), e );
            } catch ( AuthenticationException e ) {
                Logger.Exception( this, String.Format( "acceptClient AuthenticationException: [{0}], [{1}]", e.Message, e.InnerException.Message ), e );
            } catch ( Exception e ) {
                Logger.Exception( this, String.Format( "acceptClient Exception: [{0}]", e.Message ), e );
            } finally {
                this.listenerStart( listener );
            }
        }

        internal void RemoteDisconnected ( Client client ) {
            this.clientList.Remove( client );
            Logger.Debug( this, "clients connected: {0}", this.clientList.Count );
            client.Dispose( );
        }

        internal void ResponseIQ ( QueryFromClient item ) {
            //throw new NotImplementedException( );
            item.Sender.Queue[QueueDirection.Out].Enqueue( item.Query );
        }
    }
}
